/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ssp.agent;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import ssp.database.DatabaseManager;
import ssp.database.NetworkMap;
import ssp.event.EventQueue;
import ssp.net.NWcomponent;
import ssp.service.FrontendServer;
import ssp.strategy.agent.Agent_v1_Strategy;
import ssp.topology.Node;
import ssp.utility.LoggingManager;
import ssp.utility.Pair;
import ssp.utility.PropertyManager;

/**
 * エージェントを表すクラス
 * @author yamazaki
 */
public final class Agent_v1 {
    
    /**
     * エージェントを送出したユーザが属するネットワークコンポーネント                                                                                                                                                       
     */
    private final NWcomponent belongingNWcomponent;
        
    /**
     * サービス生成のためのストラテジ
     */
    private final Agent_v1_Strategy strategy;
    
    /**
     * ユーザのネットワーク品質/サービス品質に関する要求
     */
    private final QualityDemand qualityDemand;
    
    /**
     * ユーザのサービス要求を満たすフロントエンドサーバの候補リスト
     */
    private List<FrontendServer> candidateServiceList;
    
    /**
     * ユーザのサービス要求/ネットワーク要求を満たす候補経路のマップ
     */
    private Map<Double, Pair<FrontendServer, List<NWcomponent>>> candidatePath;
    
    /**
     * コンストラクタ
     * @param belongingNWcomponent ユーザの属するネットワークコンポーネント
     * @param qualityDemand ユーザの品質要求
     * @param strategy サービス生成のためのストラテジ
     */
    public Agent_v1(NWcomponent belongingNWcomponent, QualityDemand qualityDemand, Agent_v1_Strategy strategy) {
        this.belongingNWcomponent = belongingNWcomponent;
        this.qualityDemand = qualityDemand;
        this.candidateServiceList = new ArrayList<>();
        this.candidatePath = new TreeMap<>();
        this.strategy = strategy;
    }
    
    /**
     * strategyに従ってサービスを生成する
     * @param hopNum ネットワークマップから取得するフロントエンドサーバのユーザからのホップ数
     * @param chargeNum サービスマップから取得するフロントエンドサーバ数
     * @return サービス生成に成功すればtrue
     */
    public boolean createServicePath(int hopNum, int chargeNum) {
        
        //フロントエンドサーバ探索
        searchFrontendServer(hopNum, chargeNum);
        
        //hopNum以内にユーザの要求を満たすサービスを提供しているフロントエンドサーバが存在しない
        if(candidateServiceList.isEmpty()) {
//            LoggingManager.logging(Level.INFO, "There exists no services which meet user's demand within " + hopNum + " hop.", new Throwable());
            if(EventQueue.getINSTANCE().isIsLogging())
                LoggingManager.logging(Level.INFO, "There exists no services which meet user's demand within " + hopNum + " hop.", null);
            return false;
        }
        
        //ネットワークコンポーネント探索
        searchPath();
        
        //指定のストラテジがユーザの要求を満たすネットワークコンポーネントの組み合わせを発見できない
        if(candidatePath.isEmpty()) {
            StringBuilder buffer = new StringBuilder(200);
            buffer.append("The ");
            buffer.append(this.strategy.toString());
            buffer.append(" cannot find the path which meets user's demand. NWdemand: ");
            for(double index : qualityDemand.getNetworkQualityList()) {
                buffer.append(index);
                buffer.append(", ");
            }
//            LoggingManager.logging(Level.INFO, buffer.toString(), new Throwable());
            if(EventQueue.getINSTANCE().isIsLogging())
                LoggingManager.logging(Level.INFO, buffer.toString(), null);
            return false;
        }
        
        //サービス生成
        return makeService();
    }
    
    /**
     * ユーザのサービス品質要求を満たすフロントエンドサーバを探索する
     * @param hopNum ネットワークマップから取得するフロントエンドサーバのユーザからのホップ数
     * @param chargeNum サービスマップから取得するフロントエンドサーバ数
     */
    private void searchFrontendServer(int hopNum, int chargeNum) {
        candidateServiceList = strategy.searchFrontendServer(qualityDemand, belongingNWcomponent, hopNum, chargeNum);
        LoggingManager.logging(Level.FINEST, "The size of candidateServiceList: " + this.candidateServiceList.size(), new Throwable());
    }
    
    /**
     * 候補フロントエンドサーバからユーザまでのネットワーク品質要求を満たすネットワークコンポーネントの組み合わせを探索する
     */
    private void searchPath() {
        for(FrontendServer frontendServer : candidateServiceList) {
            
            //strategyに従った探索
            Pair<FrontendServer, List<NWcomponent>> path = strategy.searchPath(qualityDemand, belongingNWcomponent, frontendServer);
            
            //探索に失敗
            if(path == null) {
                continue;
            }
            
            //探索したNWコンポーネントの組み合わせの重みを計算
            double weight = DatabaseManager.getINSTANCE().getServiceMap().getRecipAvailability(path.getFirst().getID()) * PropertyManager.getValueAsDouble("PathWeight");
            NWcomponent prevNode = null;
            NWcomponent currentNode = null;
            NetworkMap NWDB = DatabaseManager.getINSTANCE().getNetworkMap();
            
            // 空き容量の逆数が重み
            Set<Node> connectedNodes = new HashSet<>();
            for(NWcomponent node : path.getSecond()) {
                if(prevNode == null) {
                    prevNode = node;
                } else if(currentNode == null) {
                    currentNode = node;
                    connectedNodes.add(node);
                    connectedNodes.add(prevNode.getEndsOfInternalPath(node));
                    weight += NWDB.getRecipAvailability(new Pair(prevNode.getID(), connectedNodes));
                    connectedNodes.clear();
                } else {
                    connectedNodes.add(prevNode);
                    connectedNodes.add(node);
                    weight += NWDB.getRecipAvailability(new Pair(currentNode.getID(), connectedNodes));
                    
                    prevNode = currentNode;
                    currentNode = node;
                    connectedNodes.clear();
                }
            }
            
            this.candidatePath.put(weight, path);

        }
    }
    
    /**
     * 実際にリソースを予約する
     * @return 予約に成功すればtrue
     */
    private boolean makeService() {
        for(Pair<FrontendServer, List<NWcomponent>> targetPath : candidatePath.values()) {
            if(!isMargin2(targetPath)) {
                if(EventQueue.getINSTANCE().isIsLogging())
                    LoggingManager.logging(Level.INFO, "There may exist no margin to reserve resources.", new Throwable());
                continue;
            }
            if(strategy.makeService(targetPath.getFirst(), targetPath.getSecond(), qualityDemand)) {
                return true;
            }
        }
        
//        LoggingManager.logging(Level.INFO, "This call is lost because of availabillity.", new Throwable());
        if(EventQueue.getINSTANCE().isIsLogging())
            LoggingManager.logging(Level.INFO, "This call is lost because of availabillity.", null);
        return false;
    }
    
    /**
     * @deprecated 
     * DBを参照してリソースが逼迫しているか否かを返す
     * @param target 予約しようとしているリソース
     * @return 逼迫していればfalse
     */
    private boolean isMargin(Pair<FrontendServer, List<NWcomponent>> target) {
        
        //フロントエンドサーバが逼迫
        if(DatabaseManager.getINSTANCE().getServiceMap().getAvailability(target.getFirst().getID()) < PropertyManager.getValueAsDouble("FrontendServerMargin")) {
            return false;
        }
        
        //途中のネットワークコンポーネントが逼迫
        NWcomponent prevNode = null;
        NWcomponent currentNode = null;
        for(NWcomponent node : target.getSecond()) {
            if(prevNode == null) {
                prevNode = node;
            } else if(currentNode == null) {
                currentNode = node;
            } else {
                Set<Node> connectedNodes = new HashSet<>();
                connectedNodes.add(prevNode);
                connectedNodes.add(node);
                Pair<Integer, Set<Node>> key = new Pair<>(currentNode.getID(), connectedNodes);
                
                if(DatabaseManager.getINSTANCE().getNetworkMap().getAvailability(key) < PropertyManager.getValueAsDouble("NetworkComponentMargin")) {
                    return false;
                }

                prevNode = currentNode;
                currentNode = node;
            }
        }
        
        return true;
    }
    
    /**
     * DBを参照してリソースが逼迫しているか否かを返す
     * @param target 予約しようとしているリソース
     * @return 逼迫していればfalse
     */
    private boolean isMargin2(Pair<FrontendServer, List<NWcomponent>> target) {
        
        //フロントエンドサーバが逼迫
        if(DatabaseManager.getINSTANCE().getServiceMap().getAvailability(target.getFirst().getID()) < target.getFirst().getServiceEntry(qualityDemand.getTypeID()).getWorkload() + PropertyManager.getValueAsDouble("FrontendServerMargin")) {
            return false;
        }
        
        //ネットワークコンポーネントが逼迫
        if(target.getSecond().size() == 1) {
            return true;
        }
        NWcomponent prevNode = null;
        NWcomponent currentNode = null;
        for(NWcomponent node : target.getSecond()) {
            if(prevNode == null) {
                prevNode = node;
            } else if(currentNode == null) {
                currentNode = node;
                Set<Node> connectedNodes = new HashSet<>();
                connectedNodes.add(node);
                connectedNodes.add(prevNode.getEndsOfInternalPath(node));
                Pair<Integer, Set<Node>> key = new Pair<>(prevNode.getID(), connectedNodes);
                if(DatabaseManager.getINSTANCE().getNetworkMap().getAvailability(key) < Math.ceil(qualityDemand.getBandwidth() / DatabaseManager.getINSTANCE().getNetworkMap().getBandwidthUnit(key)) + PropertyManager.getValueAsDouble("NetworkComponentMargin")) {
                    return false;
                }
            } else {
                Set<Node> connectedNodes = new HashSet<>();
                connectedNodes.add(prevNode);
                connectedNodes.add(node);
                Pair<Integer, Set<Node>> key = new Pair<>(currentNode.getID(), connectedNodes);
                
                if(DatabaseManager.getINSTANCE().getNetworkMap().getAvailability(key) < Math.ceil(qualityDemand.getBandwidth() / DatabaseManager.getINSTANCE().getNetworkMap().getBandwidthUnit(key)) + PropertyManager.getValueAsDouble("NetworkComponentMargin")) {
                    return false;
                }
                
                prevNode = currentNode;
                currentNode = node;
            }
        }
        Set<Node> connectedNodes = new HashSet<>();
        connectedNodes.add(prevNode);
        connectedNodes.add(currentNode.getEndsOfInternalPath(prevNode));
        Pair<Integer, Set<Node>> key = new Pair<>(currentNode.getID(), connectedNodes);
        if(DatabaseManager.getINSTANCE().getNetworkMap().getAvailability(key) < Math.ceil(qualityDemand.getBandwidth() / DatabaseManager.getINSTANCE().getNetworkMap().getBandwidthUnit(key)) + PropertyManager.getValueAsDouble("NetworkComponentMargin")) {
            return false;
        }
        
        return true;
    }
    
}
